﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
using ALDIOrganizer.Properties;

namespace ALDIOrganizer
{
    [Serializable]
    public class EmployeeInRota : IComparable
    {
        private readonly Rota ParentRefill;

        [NonSerialized]
        public Employee Employee;
        public string display;
        public double Hours;
        public int ContractHours;
        public bool IsContractHoursCalculated;
        private string PositionName;
        public Position HisPosition;
        public bool IsOffProductivity
        {
            get
            {
                return HisPosition.IsOffProd();
            }
        }

        /// <summary>
        /// Returns whether or not the _employee is a store runner
        /// </summary>
        /// <remarks>
        /// 1.  Used by the schedule-generating feature: it must be known whether or not
        ///     the _employee can run a store On his/her own, and whether or not they should
        ///     be treated as store runners When keyflow for store runners only is being calculated.
        /// 2.  It only returns true When the _employee is a store runner EVERY DAY OF THE WEEK
        ///     HE'S EMPLOYED ON. If he'pShift a store assistant On Monday, and gets a promotion to
        ///     manager On Wednesday - he is not considered a store runner for this particular week.
        ///     The application is not that "smart". It'pShift just for simplicity. I could improve it one Day,
        ///     this would take some rewriting in the Engine class. Not worth the work right now.
        /// </remarks>
        public bool IsStoreRunner
        {
            get
            {
                for (var day = 0; day < 7; day++)
                {
                    if (ContractStatuses.Value[day] == null)
                    {
                        continue;
                    }
                    if (!(ContractStatuses.Value[day].role.IsStoreRunner()))
                    {
                        return false;
                    }
                }
                return true;
            }
        }
        public KeyValuePair<bool[], Contract[]> ContractStatuses;
        public KeyValuePair<bool[], Holiday[]> HolidayStatuses;
        public KeyValuePair<bool[], List<Request>[]> RequestStatuses;

        public EmployeeInRota(Employee e, Rota pRefill)
        {
            this.Employee = e;
            this.ParentRefill = pRefill;
        }

        public void RecalculateHours(List<Shift> pShifts)
        {
            var hours = new SumOfHours();
            foreach (var shift in pShifts)
            {
                hours += shift.Length();
            }
            Hours = hours.GetTotal;
        }

        public void RecalculateContractsHolidaysAndPosition(bool pContractsPosition, bool pHolidays, bool pRequests, DateTime pMonday)
        {
            if (pHolidays)
            {
                HolidayStatuses = SQLiteAccess.GetHolidaysForWeek(
                    Employee.ID,
                    pMonday,
                    true);
            }
            if (pRequests)
            {
                RequestStatuses = SQLiteAccess.GetRequestsForWeek(
                    Employee.ID,
                    pMonday);
            }
            if (pContractsPosition)
            {
                ContractStatuses = SQLiteAccess.GetContractsForWeek(
                    Employee.ID,
                    pMonday);
                var result = SQLiteAccess.ComputeWorkingHoursForWeek(
                    Employee.ID,
                    pMonday);
                ContractHours = result.Key;
                // correcting if the employee is on holidays all week - 11111111
                if (HolidayStatuses.Key.All(bit => bit))
                {
                    ContractHours = 0;
                    IsContractHoursCalculated = true;
                }
                IsContractHoursCalculated |= result.Value;
                RecalculatePosition();
            }
            
                       
        }

        /// <summary>
        /// Is the _employee available to work this particular Shift (not fired? not On holidays? hasn't requested not to?)
        /// </summary>
        /// <param name="pShift">The Shift in question for the _employee to work</param>        
        /// <returns></returns>
        /// <remarks>
        /// If pShift is On a Rota, we don't really need to pass pMonday, because such a Shift
        /// "knows" what calendar Day it belongs to (it will find out via its GetParent property).
        /// 
        /// However, if pShift belongs to a Template - it doesn't know that, because Template class
        /// is "timeless" - it is not connected to any particular week.
        /// 
        /// This 
        /// </remarks>
        public bool CanWork(Shift pShift)
        {
            #region bug detector
            if (pShift.Parent.Mode == SheetType.Rota && (pShift.When < ParentRefill.Monday)
                | pShift.When > ParentRefill.Monday.AddDays(6))
            {
                return false;
            }
            #endregion

            var day = pShift.Day.ToInt0();
            if (!ContractStatuses.Key[day])
            {
                return false;
            }
            if (HolidayStatuses.Key[day])
            {
                return false;
            }
            if (RequestStatuses.Key[day] &&
                RequestStatuses.Value[day].ToArray().DoesAnyCollideWith(pShift, false))
            {
                return false;
            }
            return true;
        }

        /// <summary>
        /// Refreshes Value of the Position using values from contractStatuses field.
        /// </summary>
        /// <remarks>
        /// 1. It uses the Value of contractStatuses field. It doesn't "know" whether contractStatuses is up to date.
        /// That'pShift why it'pShift private.
        /// 2. An _employee may be Hired On different work positions throughout the week
        /// (for example, a store assistant Monday-Wednesday, then signed a store manager _contract On Thursday).
        /// In such case the resulting Position string will be "SA/SM".
        /// If an _employee did not even work for first few pDays, his Position will be set to - for example "-/SD".
        /// </remarks>
        private void RecalculatePosition()
        {
            var positions = new List<string>();
            var bottomPosition = Position.None; // maximum

            foreach (var contract in ContractStatuses.Value)
            {
                var toAdd = String.Empty;
                if (contract != null &&
                    contract.Hired)
                {
                    toAdd = contract.role.ToShortString();

                    if ((int)contract.role < (int)bottomPosition |
                        bottomPosition == Position.None)
                    {
                        bottomPosition = contract.role;
                    }
                }

                // we add another to the list only if:
                if ((positions.Count > 0 && toAdd != positions.Last()) ||
                    // there is some previous one, and it's different than current one
                    (positions.Count == 0) && toAdd != String.Empty)
                {
                    positions.Add(toAdd);
                }
            }

            HisPosition = bottomPosition;

            if (positions.Count > 1)
            {
                PositionName = String.Join("/", positions.ToArray());
            }
            if (positions.Count == 1)
            {
                PositionName = positions[0];
            }
            if (positions.Count == 0)
            {
                PositionName = "-";
            }
        }

        public override string ToString()
        {
            var stringBuilder = new StringBuilder();
            var bits = Settings.Default.FORMAT_EmployeeCell.Split(';');
            foreach (var bit in bits)
            {
                stringBuilder.Append(ReturnBit(bit));
            }
            return stringBuilder.ToString();            
        }

        private string ReturnBit(string pBitName)
        {
            switch (pBitName.ToLower())
            {
                case "n":
                    {
                        return Environment.NewLine;
                    }
                case "fn":
                    {
                        return Employee.firstName;
                    }
                case "fnu":
                    {
                        return Employee.firstName.ToUpper();
                    }
                case "ln":
                    {
                        return Employee.lastName;
                    }
                case "lnu":
                    {
                        return Employee.lastName.ToUpper();
                    }
                case "pos":
                    {
                        return PositionName;
                    }
                case "hrs":
                    {
                        return Hours.ToString();
                    }
                case "chrs":
                    {
                        return ContractHours +
                               (Employee.OnVisa
                                   ? Resources.SHEET_Employee_Visa
                                   : String.Empty);
                    }
                case "calc":
                    {
                        return IsContractHoursCalculated ?
                            Resources.CALCULATED_VALUE_INDICATOR :
                            String.Empty;
                    }
                default: // (literal)
                    {
                        return pBitName;
                    }
            }
        }

        #region IComparable Members

        public int CompareTo(object obj)
        {
            if (!(obj is EmployeeInRota))
            {
                return 0;
            }

            var eir2 = obj as EmployeeInRota;

            if (HisPosition == eir2.HisPosition)
            {
                return Employee.lastName() == eir2.Employee.lastName ?
                    Employee.FirstName().CompareTo(eir2.Employee.firstName) :
                    Employee.lastName().CompareTo(eir2.Employee.lastName);
            }
            // ^ means XOR in C#.
            // basically, we're verifying whether their off-productivity status is the same or not.
            if (HisPosition.IsOffProd() ^ eir2.HisPosition.IsOffProd())
            {
                return HisPosition.IsOffProd() ? 1 : -1;
            }
            // if it's different, then the one who's off productivity is "bigger" (lower On the list)
            var r1 = (int)HisPosition.GetAsOnProd();
            var r2 = (int)eir2.HisPosition.GetAsOnProd();
            return r2.CompareTo(r1); // reversed, because store manager (32) should be smaller than assistant manager (16)                    
            // REMEMBER: sorting will be ascending, so "smaller" means "goes first", "higher On the list" etc.!!!

        }

        #endregion
    }
}
